summaryrefslogtreecommitdiff
path: root/app/[lng]/evcp/(evcp)
diff options
context:
space:
mode:
Diffstat (limited to 'app/[lng]/evcp/(evcp)')
-rw-r--r--app/[lng]/evcp/(evcp)/(master-data)/projects/page.tsx2
-rw-r--r--app/[lng]/evcp/(evcp)/(procurement)/po/[id]/contract-detail-client.tsx143
-rw-r--r--app/[lng]/evcp/(evcp)/(procurement)/po/[id]/page.tsx56
3 files changed, 200 insertions, 1 deletions
diff --git a/app/[lng]/evcp/(evcp)/(master-data)/projects/page.tsx b/app/[lng]/evcp/(evcp)/(master-data)/projects/page.tsx
index c3d429d1..c9e6b0e3 100644
--- a/app/[lng]/evcp/(evcp)/(master-data)/projects/page.tsx
+++ b/app/[lng]/evcp/(evcp)/(master-data)/projects/page.tsx
@@ -36,7 +36,7 @@ export default async function IndexPage(props: IndexPageProps) {
<div>
<div className="flex items-center gap-2">
<h2 className="text-2xl font-bold tracking-tight">
- 프로젝트 리스트 from S-EDP
+ 프로젝트 리스트
</h2>
<InformationButton pagePath="evcp/projects" />
</div>
diff --git a/app/[lng]/evcp/(evcp)/(procurement)/po/[id]/contract-detail-client.tsx b/app/[lng]/evcp/(evcp)/(procurement)/po/[id]/contract-detail-client.tsx
new file mode 100644
index 00000000..28a85e50
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/(procurement)/po/[id]/contract-detail-client.tsx
@@ -0,0 +1,143 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { ChevronLeft } from "lucide-react"
+import Link from "next/link"
+import { toast } from "sonner"
+import { saveSHIComment } from "@/lib/po/vendor-table/service"
+import { useRouter } from "next/navigation"
+import { ContractInfoCard } from "@/components/contract/contract-info-card"
+import { ContractItemsCard } from "@/components/contract/contract-items-card"
+import { Alert, AlertDescription } from "@/components/ui/alert"
+import { AlertCircle } from "lucide-react"
+
+interface ContractDetailClientProps {
+ contract: any
+ lng: string
+}
+
+export function ContractDetailClient({ contract, lng }: ContractDetailClientProps) {
+ const router = useRouter()
+ const [shiComment, setSHIComment] = React.useState(contract.shiComment || "")
+ const [isLoading, setIsLoading] = React.useState(false)
+
+ const handleSaveComment = async () => {
+ try {
+ setIsLoading(true)
+ const result = await saveSHIComment(contract.id, shiComment)
+ if (result.success) {
+ toast.success(result.message)
+ router.refresh()
+ } else {
+ toast.error("의견 저장에 실패했습니다.")
+ }
+ } catch (error) {
+ toast.error("의견 저장 중 오류가 발생했습니다.")
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ return (
+ <>
+ {/* 헤더 */}
+ <div className="flex items-center justify-between">
+ <div className="flex items-center gap-4">
+ <Link href={`/${lng}/evcp/po`}>
+ <Button variant="ghost" size="icon">
+ <ChevronLeft className="h-5 w-5" />
+ </Button>
+ </Link>
+ <div className="flex items-center gap-3">
+ <h1 className="text-2xl font-bold tracking-tight">계약 상세</h1>
+ <div className="flex items-center gap-2">
+ <span className="text-sm text-muted-foreground">계약번호:</span>
+ <span className="text-sm font-medium">{contract.contractNo}</span>
+ <Badge variant="outline" className="ml-2">
+ {contract.status}
+ </Badge>
+ </div>
+ </div>
+ </div>
+ <div className="flex gap-2">
+ <Button
+ variant="default"
+ onClick={handleSaveComment}
+ disabled={isLoading}
+ >
+ 의견저장
+ </Button>
+ </div>
+ </div>
+
+ {/* 계약 거절 사유 표시 (있는 경우) */}
+ {contract.rejectionReason && (
+ <Alert variant="destructive">
+ <AlertCircle className="h-4 w-4" />
+ <AlertDescription>
+ <strong>계약 거절 사유:</strong> {contract.rejectionReason}
+ </AlertDescription>
+ </Alert>
+ )}
+
+ {/* 계약 조건 */}
+ <ContractInfoCard contract={contract} />
+
+ {/* 코멘트 */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="text-lg">코멘트</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
+ <div className="space-y-2">
+ <label htmlFor="vendor-comment" className="text-sm font-medium text-muted-foreground">
+ Vendor Comment
+ </label>
+ <div className="w-full min-h-[100px] px-3 py-2 text-sm border rounded-md bg-muted/50">
+ {contract.vendorComment || "코멘트가 없습니다."}
+ </div>
+ </div>
+ <div className="space-y-2">
+ <label htmlFor="shi-comment" className="text-sm font-medium">
+ SHI Comment
+ </label>
+ <textarea
+ id="shi-comment"
+ className="w-full min-h-[100px] px-3 py-2 text-sm border rounded-md resize-none focus:outline-none focus:ring-2 focus:ring-ring"
+ placeholder="SHI 코멘트를 입력하세요..."
+ value={shiComment}
+ onChange={(e) => setSHIComment(e.target.value)}
+ />
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+
+ {/* 계약문서 */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="text-lg">계약문서</CardTitle>
+ </CardHeader>
+ <CardContent>
+ {contract.contractContent ? (
+ <div className="prose prose-sm max-w-none">
+ <pre className="whitespace-pre-wrap text-sm bg-muted/50 p-4 rounded-md">
+ {contract.contractContent}
+ </pre>
+ </div>
+ ) : (
+ <p className="text-sm text-muted-foreground">계약문서 내용이 없습니다.</p>
+ )}
+ </CardContent>
+ </Card>
+
+ {/* 계약 품목 */}
+ <ContractItemsCard items={contract.items || []} currency={contract.currency} />
+ </>
+ )
+}
+
diff --git a/app/[lng]/evcp/(evcp)/(procurement)/po/[id]/page.tsx b/app/[lng]/evcp/(evcp)/(procurement)/po/[id]/page.tsx
new file mode 100644
index 00000000..226d960d
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/(procurement)/po/[id]/page.tsx
@@ -0,0 +1,56 @@
+import * as React from "react"
+import { getServerSession } from "next-auth/next"
+import { authOptions } from "@/app/api/auth/[...nextauth]/route"
+import { redirect } from "next/navigation"
+import { getContractDetail } from "@/lib/po/vendor-table/service"
+import { Shell } from "@/components/shell"
+import { ContractDetailClient } from "./contract-detail-client"
+
+interface ContractDetailPageProps {
+ params: Promise<{
+ id: string
+ lng: string
+ }>
+}
+
+export default async function EVCPContractDetailPage(props: ContractDetailPageProps) {
+ const params = await props.params
+ const contractId = parseInt(params.id, 10)
+
+ // 유효하지 않은 ID 체크
+ if (isNaN(contractId) || contractId <= 0) {
+ return (
+ <Shell className="gap-4">
+ <div className="flex h-full items-center justify-center p-6">
+ <p className="text-muted-foreground">유효하지 않은 계약 ID입니다.</p>
+ </div>
+ </Shell>
+ )
+ }
+
+ // 세션 체크 (EVCP 사용자만 접근 가능)
+ const session = await getServerSession(authOptions)
+ if (!session?.user) {
+ redirect("/")
+ }
+
+ // 계약 상세 정보 조회
+ const result = await getContractDetail(contractId)
+
+ if (!result.success || !result.data) {
+ return (
+ <Shell className="gap-4">
+ <div className="flex h-full items-center justify-center p-6">
+ <p className="text-muted-foreground">{result.error || "계약 정보를 찾을 수 없습니다."}</p>
+ </div>
+ </Shell>
+ )
+ }
+
+ return (
+ <Shell className="gap-4">
+ <ContractDetailClient contract={result.data} lng={params.lng} />
+ </Shell>
+ )
+}
+